{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "nbsphinx": "hidden"
   },
   "source": [
    "This notebook is part of the `clifford` documentation: https://clifford.readthedocs.io/."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Visualization tools\n",
    "\n",
    "In this example we will look at some external tools that can be used with `clifford` to help visualize geometric objects.\n",
    "\n",
    "The two tools available are:\n",
    "\n",
    "* `pyganja` ([github](https://github.com/pygae/pyganja))\n",
    "* `mpl_toolkits.clifford` ([github](https://github.com/pygae/mpl_toolkits.clifford))\n",
    "\n",
    "Both of these can be installed with `pip install` followed by the package name above."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## G2C\n",
    "\n",
    "Let's start by creating some objects in 2d Conformal Geometric Algebra to visualize:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from clifford.g2c import *"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "outputs": [],
   "source": [
    "point = up(2*e1+e2)\n",
    "line = up(3*e1 + 2*e2) ^ up(3*e1 - 2*e2) ^ einf\n",
    "circle = up(e1) ^ up(-e1 + 2*e2) ^ up(-e1 - 2*e2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We'll create copies of the point and line reflected in the circle, using $X = C\\hat X\\tilde C$, where $\\hat X$ is the grade involution."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "point_refl = circle * point.gradeInvol() * ~circle\n",
    "line_refl = circle * line.gradeInvol() * ~circle"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### ``pyganja``\n",
    "\n",
    "pyganja is a python interface to the `ganja.js` ([github](https://github.com/enkimute/ganja.js)) library.\n",
    "To use it, typically we need to import two names from the library:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pyganja import GanjaScene, draw\n",
    "import pyganja; pyganja.__version__"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`GanjaScene` lets us build scenes out of geometric objects, with attached labels and RGB colors:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sc = GanjaScene()\n",
    "sc.add_object(point, color=(255, 0, 0), label='point')\n",
    "sc.add_object(line, color=(0, 255, 0), label='line')\n",
    "sc.add_object(circle, color=(0, 0, 255), label='circle')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sc_refl = GanjaScene()\n",
    "sc_refl.add_object(point_refl, color=(128, 0, 0), label='point_refl')\n",
    "sc_refl.add_object(line_refl, color=(0, 128, 0), label='line_refl')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Once we've built our scene, we can `draw` it, specifying a `scale` (which here we use to zoom out), and the signature of our algebra (which defaults to conformal 3D):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "draw(sc, sig=layout.sig, scale=0.5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A cool feature of ``GanjaScene`` is the ability to use `+` to draw both scenes together:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "draw(sc + sc_refl, sig=layout.sig, scale=0.5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### `mpl_toolkits.clifford`\n",
    "\n",
    "While `ganja.js` produces great diagrams, it's hard to combine them with other plotting tools.\n",
    "`mpl_toolkits.clifford` works within `matplotlib`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from matplotlib import pyplot as plt\n",
    "plt.ioff()  # we'll ask for plotting when we want it\n",
    "\n",
    "# if you're editing this locally, you'll get an interactive UI if you uncomment the following\n",
    "#\n",
    "#    %matplotlib notebook\n",
    "\n",
    "from mpl_toolkits.clifford import plot\n",
    "import mpl_toolkits.clifford; mpl_toolkits.clifford.__version__"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Assembling the plot is a lot more work, but we also get much more control:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "# standard matplotlib stuff - construct empty plots side-by-side, and set the scaling\n",
    "fig, (ax_before, ax_both) = plt.subplots(1, 2, sharex=True, sharey=True)\n",
    "ax_before.set(xlim=[-4, 4], ylim=[-4, 4], aspect='equal')\n",
    "ax_both.set(xlim=[-4, 4], ylim=[-4, 4], aspect='equal')\n",
    "\n",
    "# plot the objects before reflection on both plots\n",
    "for ax in (ax_before, ax_both):\n",
    "    plot(ax, [point], color='tab:blue', label='point', marker='x', linestyle=' ')\n",
    "    plot(ax, [line], color='tab:green', label='line')\n",
    "    plot(ax, [circle], color='tab:red', label='circle')\n",
    "\n",
    "# plot the objects after reflection, with thicker lines\n",
    "plot(ax_both, [point_refl], color='tab:blue', label='point_refl',  marker='x', linestyle=' ', markeredgewidth=2)\n",
    "plot(ax_both, [line_refl], color='tab:green', label='line_refl', linewidth=2)\n",
    "\n",
    "fig.tight_layout()\n",
    "ax_both.legend()\n",
    "\n",
    "# show the figure\n",
    "fig"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## G3C\n",
    "\n",
    "Let's repeat the above, but with 3D Conformal Geometric Algebra.\n",
    "Note that if you're viewing these docs in a jupyter notebook, the lines below will replace all your 2d variables with 3d ones"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from clifford.g3c import *"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "point = up(2*e1+e2)\n",
    "line = up(3*e1 + 2*e2) ^ up(3*e1 - 2*e2) ^ einf\n",
    "circle = up(e1) ^ up(-e1 + 1.6*e2 + 1.2*e3) ^ up(-e1 - 1.6*e2 - 1.2*e3)\n",
    "sphere = up(3*e1) ^ up(e1) ^ up(2*e1 + e2) ^ up(2*e1 + e3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# note that due to floating point rounding, we need to truncate back to a single grade here, with ``(grade)``\n",
    "point_refl = homo((circle * point.gradeInvol() * ~circle)(1))\n",
    "line_refl = (circle * line.gradeInvol() * ~circle)(3)\n",
    "sphere_refl = (circle * sphere.gradeInvol() * ~circle)(4)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### ``pyganja``"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Once again, we can create a pair of scenes exactly as before"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sc = GanjaScene()\n",
    "sc.add_object(point, color=(255, 0, 0), label='point')\n",
    "sc.add_object(line, color=(0, 255, 0), label='line')\n",
    "sc.add_object(circle, color=(0, 0, 255), label='circle')\n",
    "sc.add_object(sphere, color=(0, 255, 255), label='sphere')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sc_refl = GanjaScene()\n",
    "sc_refl.add_object(point_refl, color=(128, 0, 0), label='point_refl')\n",
    "sc_refl.add_object(line_refl.normal(), color=(0, 128, 0), label='line_refl')\n",
    "sc_refl.add_object(sphere_refl.normal(), color=(0, 128, 128), label='sphere_refl')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But this time, when we draw them we don't need to pass `sig`.\n",
    "Better yet, we can rotate the 3D world around using left click, pan with right click, and zoom with the scroll wheel."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "draw(sc + sc_refl, scale=0.5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Some more example of using `pyganja` to visualize 3D CGA can be found in the [interpolation](./interpolation.ipynb) and [clustering](./clustering.ipynb) notebooks."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### `mpl_toolkits.clifford`\n",
    "\n",
    "The 3D approach for `matplotlib` is much the same.\n",
    "Note that due to poor handling of rounding errors in `clifford.tools.classify`, a call to `.normal()` is needed.\n",
    "Along with explicit grade selection, this is a useful trick to try and get something to render which otherwise would not."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# standard matplotlib stuff - construct empty plots side-by-side, and set the scaling\n",
    "fig, (ax_before, ax_both) = plt.subplots(1, 2, subplot_kw=dict(projection='3d'), figsize=(8, 4))\n",
    "ax_before.set(xlim=[-4, 4], ylim=[-4, 4], zlim=[-4, 4])\n",
    "ax_both.set(xlim=[-4, 4], ylim=[-4, 4], zlim=[-4, 4])\n",
    "\n",
    "# plot the objects before reflection on both plots\n",
    "for ax in (ax_before, ax_both):\n",
    "    plot(ax, [point], color='tab:red', label='point', marker='x', linestyle=' ')\n",
    "    plot(ax, [line], color='tab:green', label='line')\n",
    "    plot(ax, [circle], color='tab:blue', label='circle')\n",
    "    plot(ax, [sphere], color='tab:cyan')  # labels do not work for spheres: pygae/mpl_toolkits.clifford#5\n",
    "\n",
    "# plot the objects after reflection\n",
    "plot(ax_both, [point_refl], color='tab:red', label='point_refl', marker='x', linestyle=' ', markeredgewidth=2)\n",
    "plot(ax_both, [line_refl.normal()], color='tab:green', label='line_refl', linewidth=2)\n",
    "plot(ax_both, [sphere_refl], color='tab:cyan')\n",
    "\n",
    "fig.tight_layout()\n",
    "ax_both.legend()\n",
    "\n",
    "# show the figure\n",
    "fig"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Some more example of using `mpl_toolkits.clifford` to visualize 3D CGA can be found in the `examples` folder of the `mpl_toolkits.clifford` repositiory, [here](https://github.com/pygae/mpl_toolkits.clifford/tree/master/examples)."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
